#include "bsp_eeprom_at24cxx.h"


	
/* 定义IO = 1和 0的代码  */
#define I2C_SDA_1()		AT24CXX_SDA_PORT->BSRR = AT24CXX_SDA_PIN
#define I2C_SDA_0()		AT24CXX_SDA_PORT->BSRR = AT24CXX_SDA_PIN << 16;

#define I2C_SCL_1()		AT24CXX_SCL_PORT->BSRR = AT24CXX_SCL_PIN
#define I2C_SCL_0()		AT24CXX_SCL_PORT->BSRR = AT24CXX_SCL_PIN << 16;

#define I2C_SDA_READ()  ((AT24CXX_SDA_PORT->IDR & AT24CXX_SDA_PIN) != 0)	/* 读SDA口线状态 */
#define I2C_SCL_READ()  ((AT24CXX_SCL_PORT->IDR & AT24CXX_SCL_PIN) != 0)	/* 读SCL口线状态 */

#define I2C_WR	0		/* 写控制bit */
#define I2C_RD	1		/* 读控制bit */


//------------------------------------内部函数------------------------------------//
static void I2C_Start(void);				// I2C_开始信号	(重开始信号用此替代)
static void I2C_Stop(void); 				// I2C_结束信号
static uint8_t I2C_WaitAck(void);		    // I2C_等待应答		返回值: 0:NACK 1:ACK 
static void I2C_Ack(void);					// I2C_应答
static void I2C_NAck(void);					// I2C_不应答
static void I2C_SendByte(uint8_t _ucByte);	// I2C_写数据函数	参数:要写入的数据
static uint8_t I2C_ReadByte(void);		    // I2C_读数据函数	返回值:读到的数据
static void I2C_Delay(void);				// I2C_延时
uint8_t I2C_CheckDevice(uint8_t _Address); 

#define Adjust 12
static void HAL_Delay_us(uint32_t us, uint16_t CLK_MHz )
{
    uint32_t Delay = us * CLK_MHz / Adjust;
    do
    {
        __NOP();
    } while (Delay --);
}


/*
*********************************************************************************************************
*	函 数 名: eeprom_24xx_ConfigGPIO
*	功能说明: 配置eeprom控制
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
static void eeprom_at24cxx_ConfigGPIO(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    /* 打开GPIO时钟 */
    RCC_AHB1PeriphClockCmd(RCC_AT24CXX_PORT, ENABLE);

    /* 配置为开漏输出 */
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;		/* 设为输出口 */
    GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	 	

    GPIO_InitStructure.GPIO_Pin = AT24CXX_SDA_PIN;
    GPIO_Init(AT24CXX_SDA_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = AT24CXX_SCL_PIN;
    GPIO_Init(AT24CXX_SCL_PORT, &GPIO_InitStructure);

    /*释放SCL和SDA*/
    I2C_SDA_1();
    I2C_SCL_1();

}



/*
*********************************************************************************************************
*	函 数 名: OLED_Init
*	功能说明: 初始化OLED屏
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_eeprom_at24cxx_Init(void)
{
  eeprom_at24cxx_ConfigGPIO();

	if(0 != ee_CheckOk())
	{
		while(1);
	}
}



/*
*********************************************************************************************************
*	函 数 名: ee_CheckOk
*	功能说明: 判断串行EERPOM是否正常
*	形    参:  无
*	返 回 值: 1 表示不正常， 0 表示正常
*********************************************************************************************************
*/
uint8_t ee_CheckOk(void)
{
	if (I2C_CheckDevice(EE_DEV_ADDR) == 0)
	{
		return 0;
	}
	else
	{
		/* 失败后，切记发送I2C总线停止信号 */
		I2C_Stop();
		return 1;
	}
}


/*
*********************************************************************************************************
*	函 数 名: ee_ReadBytes
*	功能说明: 从串行EEPROM指定地址处开始读取若干数据
*	形    参:  _usAddress : 起始地址
*			 _usSize : 数据长度，单位为字节
*			 _pReadBuf : 存放读到的数据的缓冲区指针
*	返 回 值: 0 表示失败，1表示成功
*********************************************************************************************************
*/
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize)
{
	uint16_t i;

	/* 采用串行EEPROM随即读取指令序列，连续读取若干字节 */

	/* 第1步：发起I2C总线启动信号 */
	I2C_Start();

	/* 第2步：发起控制字节，高7bit是地址，bit0是读写控制位，0表示写，1表示读 */
	I2C_SendByte(EE_DEV_ADDR | I2C_WR);	/* 此处是写指令 */

	/* 第3步：发送ACK */
	if (I2C_WaitAck() != 0)
	{
		goto cmd_fail;	/* EEPROM器件无应答 */
	}

	/* 第4步：发送字节地址，24C02只有256字节，因此1个字节就够了，如果是24C04以上，那么此处需要连发多个地址 */
	if (EE_ADDR_BYTES == 1)
	{
		I2C_SendByte((uint8_t)_usAddress);
		if (I2C_WaitAck() != 0)
		{
			goto cmd_fail;	/* EEPROM器件无应答 */
		}
	}
	else
	{
		I2C_SendByte(_usAddress >> 8);
		if (I2C_WaitAck() != 0)
		{
			goto cmd_fail;	/* EEPROM器件无应答 */
		}

		I2C_SendByte(_usAddress);
		if (I2C_WaitAck() != 0)
		{
			goto cmd_fail;	/* EEPROM器件无应答 */
		}
	}

	/* 第6步：重新启动I2C总线。下面开始读取数据 */
	I2C_Start();

	/* 第7步：发起控制字节，高7bit是地址，bit0是读写控制位，0表示写，1表示读 */
	I2C_SendByte(EE_DEV_ADDR | I2C_RD);	/* 此处是读指令 */

	/* 第8步：发送ACK */
	if (I2C_WaitAck() != 0)
	{
		goto cmd_fail;	/* EEPROM器件无应答 */
	}

	/* 第9步：循环读取数据 */
	for (i = 0; i < _usSize; i++)
	{
		_pReadBuf[i] = I2C_ReadByte();	/* 读1个字节 */

		/* 每读完1个字节后，需要发送Ack， 最后一个字节不需要Ack，发Nack */
		if (i != _usSize - 1)
		{
			I2C_Ack();	/* 中间字节读完后，CPU产生ACK信号(驱动SDA = 0) */
		}
		else
		{
			I2C_NAck();	/* 最后1个字节读完后，CPU产生NACK信号(驱动SDA = 1) */
		}
	}
	/* 发送I2C总线停止信号 */
	I2C_Stop();
	return 0;	/* 执行成功 */

cmd_fail: /* 命令执行失败后，切记发送停止信号，避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	I2C_Stop();
	return 1;
}

/*
*********************************************************************************************************
*	函 数 名: ee_WriteBytes
*	功能说明: 向串行EEPROM指定地址写入若干数据，采用页写操作提高写入效率
*	形    参:  _usAddress : 起始地址
*			 _usSize : 数据长度，单位为字节
*			 _pWriteBuf : 存放读到的数据的缓冲区指针
*	返 回 值: 0 表示失败，1表示成功
*********************************************************************************************************
*/
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)
{
	uint16_t i,m;
	uint16_t usAddr;

	/*
		写串行EEPROM不像读操作可以连续读取很多字节，每次写操作只能在同一个page。
		对于24xx02，page size = 8
		简单的处理方法为：按字节写操作模式，每写1个字节，都发送地址
		为了提高连续写的效率: 本函数采用page wirte操作。
	*/

	usAddr = _usAddress;
	for (i = 0; i < _usSize; i++)
	{
		/* 当发送第1个字节或是页面首地址时，需要重新发起启动信号和地址 */
		if ((i == 0) || (usAddr & (EE_PAGE_SIZE - 1)) == 0)
		{
			/*　第０步：发停止信号，启动内部写操作　*/
			I2C_Stop();

			/* 通过检查器件应答的方式，判断内部写操作是否完成, 一般小于 10ms
				CLK频率为200KHz时，查询次数为30次左右
			*/
			for (m = 0; m < 1000; m++)
			{
				/* 第1步：发起I2C总线启动信号 */
				I2C_Start();

				/* 第2步：发起控制字节，高7bit是地址，bit0是读写控制位，0表示写，1表示读 */
				I2C_SendByte(EE_DEV_ADDR | I2C_WR);	/* 此处是写指令 */

				/* 第3步：发送一个时钟，判断器件是否正确应答 */
				if (I2C_WaitAck() == 0)
				{
					break;
				}
			}
			if (m  == 1000)
			{
				goto cmd_fail;	/* EEPROM器件写超时 */
			}

			/* 第4步：发送字节地址，24C02只有256字节，因此1个字节就够了，如果是24C04以上，那么此处需要连发多个地址 */
			if (EE_ADDR_BYTES == 1)
			{
				I2C_SendByte((uint8_t)usAddr);
				if (I2C_WaitAck() != 0)
				{
					goto cmd_fail;	/* EEPROM器件无应答 */
				}
			}
			else
			{
				I2C_SendByte(usAddr >> 8);
				if (I2C_WaitAck() != 0)
				{
					goto cmd_fail;	/* EEPROM器件无应答 */
				}

				I2C_SendByte(usAddr);
				if (I2C_WaitAck() != 0)
				{
					goto cmd_fail;	/* EEPROM器件无应答 */
				}
			}
		}

		/* 第6步：开始写入数据 */
		I2C_SendByte(_pWriteBuf[i]);

		/* 第7步：发送ACK */
		if (I2C_WaitAck() != 0)
		{
			goto cmd_fail;	/* EEPROM器件无应答 */
		}

		usAddr++;	/* 地址增1 */
	}

	/* 命令执行成功，发送I2C总线停止信号 */
	I2C_Stop();
	// 写完之后必须要有5ms的延时 
	// 手册上tWR的时间是5ms 否则会写入不成功
	HAL_Delay_us(5000,168);
	return 0;

cmd_fail: /* 命令执行失败后，切记发送停止信号，避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	I2C_Stop();
	return 1;
}



/*
*********************************************************************************************************
*	函 数 名: I2C_START
*	功能说明: CPU发起I2C总线启动信号
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
static void I2C_Start(void)
{
    I2C_SDA_1();		// SDA拉高 
    I2C_SCL_1();		// SCL拉高 准备发出起始信号
    I2C_Delay(); 
    I2C_SDA_0();		// SDA拉低 发出起始信号
    I2C_Delay(); 
    I2C_SCL_0();		// SCL拉低 开始传输
    I2C_Delay(); 
}


/*
*********************************************************************************************************
*	函 数 名: I2C_STOP
*	功能说明: CPU发起I2C总线停止信号
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
static void I2C_Stop(void)
{
    I2C_SDA_0();		// SDA拉低
    I2C_SCL_1();		// SCL拉高 准备发出结束信号
    I2C_Delay();		
    I2C_SDA_1();		// SDA拉高 发出结束信号
}



/*
*********************************************************************************************************
*	函 数 名: I2C_WaitACK
*	功能说明: CPU产生一个时钟，并读取器件的ACK应答信号
*	形    参:  无
*	返 回 值: 返回0表示正确应答，1表示无器件响应
*********************************************************************************************************
*/
static uint8_t I2C_WaitAck(void)
{
    uint8_t re;

    I2C_SDA_1();		// 拉高SDA 主机释放总线
    I2C_Delay();
    I2C_SCL_1();		// 拉高SCL 
    I2C_Delay();		
    if (I2C_SDA_READ()) /* CPU读取SDA口线状态 */
    {
        re = 1;
    }
    else
    {
        re = 0;
    }
    I2C_SCL_0();
    I2C_Delay();
    return re;
}

/*
*********************************************************************************************************
*	函 数 名: i2c_Ack
*	功能说明: CPU产生一个ACK信号
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
static void I2C_Ack(void)
{
	I2C_SDA_0();	/* CPU驱动SDA = 0 */
	I2C_Delay();
	I2C_SCL_1();	/* CPU产生1个时钟 */
	I2C_Delay();
	I2C_SCL_0();
	I2C_Delay();
	I2C_SDA_1();	/* CPU释放SDA总线 */
}

/*
*********************************************************************************************************
*	函 数 名: i2c_NAck
*	功能说明: CPU产生1个NACK信号
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
static void I2C_NAck(void)
{
	I2C_SDA_1();	/* CPU驱动SDA = 1 */
	I2C_Delay();
	I2C_SCL_1();	/* CPU产生1个时钟 */
	I2C_Delay();
	I2C_SCL_0();
	I2C_Delay();
}


/*
*********************************************************************************************************
*	函 数 名: I2C_SendByte
*	功能说明: CPU向I2C总线设备发送8bit数据
*	形    参:  _ucByte ： 等待发送的字节
*	返 回 值: 无
*********************************************************************************************************
*/
static void I2C_SendByte(uint8_t _ucByte)
{
    uint8_t i;

    /* 先发送字节的高位bit7 */
    for (i = 0; i < 8; i++)
    {
        if (_ucByte & 0x80)
        {
            I2C_SDA_1();
        }
        else
        {
            I2C_SDA_0();
        }
        I2C_Delay();
        I2C_SCL_1();
        I2C_Delay();
        I2C_SCL_0();
        if (i == 7)
        {
            I2C_SDA_1(); // 释放总线
        }
        _ucByte <<= 1;	/* 左移一个bit */
        I2C_Delay();
    }
}



/*
*********************************************************************************************************
*	函 数 名: I2C_ReadByte
*	功能说明: CPU从I2C总线设备读取8bit数据
*	形    参:  无
*	返 回 值: 读到的数据
*********************************************************************************************************
*/
static uint8_t I2C_ReadByte(void)
{
	uint8_t i;
	uint8_t value;

	/* 读到第1个bit为数据的bit7 */
	value = 0;
	for (i = 0; i < 8; i++)
	{
		value <<= 1;
		I2C_SCL_1();
		I2C_Delay();
		if (I2C_SDA_READ())
		{
			value++;
		}
		I2C_SCL_0();
		I2C_Delay();
	}
	return value;
}


/*
*********************************************************************************************************
*	函 数 名: I2C_CheckDevice
*	功能说明: 检测I2C总线设备，CPU向发送设备地址，然后读取设备应答来判断该设备是否存在
*	形    参:  _Address：设备的I2C总线地址
*	返 回 值: 返回值 0 表示正确， 返回1表示未探测到
*********************************************************************************************************
*/
uint8_t I2C_CheckDevice(uint8_t _Address)
{
	uint8_t ucAck;

	if (I2C_SDA_READ() && I2C_SCL_READ())
	{
		I2C_Start();		/* 发送启动信号 */

		/* 发送设备地址+读写控制bit（0 = w， 1 = r) bit7 先传 */
		I2C_SendByte(_Address | I2C_WR);
		ucAck = I2C_WaitAck();	/* 检测设备的ACK应答 */

		I2C_Stop();			/* 发送停止信号 */

		return ucAck;
	}
	return 1;	/* I2C总线异常 */
}




// 调用
// HAL_Delay_u(1, 72);//延时1us, 时钟为72MHz
static void I2C_Delay(void)
{
    HAL_Delay_us(1,168);
}
